PROFDINFO.COM

Votre enseignant d'informatique en ligne

Bits et octets (suite)

Opérations sur les bits

La plupart des langages de programmation offrent des opérateurs qui permettent de travailler au niveau des bits. Ces opérations sont toutefois limitées aux variables entières (char et int en C++). Il ne faut pas les confondre avec leurs correspondants logiques, qui travaillent avec des true/false.

Les opérateurs "bit à bit" ont ceci de particulier qu'ils appliquent la même opération à chacun des bits correspondants des deux opérandes.

ET

L'opérateur du ET bit à bit est le "&" et sa table de vérité est :

&
0
1
0
0
0
1
0
1

Cherchons ce que vaut l'expression 214 & 116 :

 1101 0110 (214)
&0111 0100 (116)
 ---------
 0101 0100 (84)
      

L'expression 214 & 116 vaut 84.

Le & est souvent utilisé pour faire des masques, qui permettent "d'isoler" certains bits. On verra un exemple concret dans un instant.

OU

L'opérateur du OU (inclusif) est "|" et sa table de vérité est :

|
0
1
0
0
1
1
1
1

Cherchons ce que vaut l'expression 214 | 116 :

 1101 0110 (214)
|0111 0100 (116)
 ---------
 1111 0110 (246)
      

L'expression 214 | 116 vaut 246.

Le | est souvent utilisé pour "additionner" des flags afin de les passer tous ensemble en un seul nombre à une fonction dans une bibliothèque. En effet, le | peut faire office d'opération d'addition si tous les flags correspondent à des numéros qui n'utilisent pas les mêmes bits. Par exemple:

 0000 0001 (Flag 1 allumé)
|0000 0010 (Flag 2 allumé)
 ---------
 0000 0011 (Les deux flags allumés en même temps)

Opérateur de complément

Les opérateurs vus jusqu'ici sont appelés opérateurs "binaires", non parce qu'ils s'appliquent individuellement aux bits, mais parce qu'ils utilisent deux opérandes. L'opérateur suivant, celui de complément à 1, est un opérateur "unaire" dont la fonction est d'inverser les bits de son unique opérande.

Cherchons ce que vaut l'expression ~214 :

~1101 0110 = 0010 1001
      

L'expression ~214 vaut 41.

Le complément est la première étape dans la méthode du complément à 2 pour créer des nombres négatifs. Il permet également d'inverser un bit donné pour "éteindre" un flag allumé, ou vice-versa.

Opérateurs de décalage.

Ces opérateurs réalisent un décalage à droite (>>) ou à gauche (<<) sur le premier opérande. L'amplitude du décalage (en nombre de bits) est fournie par le second opérande.

Cherchons ce que vaut l'expression 214 << 3 (la valeur 214 décalée à gauche de 3 bits) :

      1101 0110    (214)
<--1101 0110       les 3 bits à gauche sont perdus
      1011 0000<-- et 3 bits à 0 apparaissent à droite
      1011 0000    (176)
      

L'expression 214 << 3 vaut 176.

Dans le cas d'un décalage vers la gauche, les bits de gauche sont perdus et des bits à 0 apparaissent à droite. Dans le cas d'un décalage vers la droite, les bits de droite sont perdus et des bits à 0 ou à 1 selon le cas apparaissent à gauche. La valeur de ces bits (0 ou 1) dépend selon que le premier opérande est une vrariable d'un type signé ou non. Si le type est non signé, des bits à 0 apparaîtront à gauche. On parle alors de "décalage logique". Si le type est signé, des bits identiques au bit de signe apparaîtront à gauche. On parle alors de "décalage arithmétique" ou de "propagation du bit de signe".

Cherchons ce que vaut l'expression 214 >> 3 (la valeur 214 décalée à droite de 3 bits). D'abord en supposant que la variable qui contient 214 est d'un type non signé :

   1101 0110       (214)
      1101 0110--> les 3 bits à droite sont perdus
-->0001 1010       et 3 bits à 0 apparaissent à gauche (décalage logique)
   0001 1010       (26)
      

L'expression 214 >>3 vaut 26 pour une variable d'un type non signé.

Maintenant en supposant que la variable qui contient 214 est d'un type signé (il s'agit donc en réalité de -42) :

   1101 0110       (-42)
      1101 0110--> les 3 bits à droite sont perdus
-->1111 1010       et 3 bits identiques au bit de signe apparaissent à gauche (décalage arithmétique)
   1111 1010       (-6)
      

L'expression -42 >>3 vaut -6 pour une variable d'un type signé.

L'utilité des opérateurs de décalage sera découverte lors des exercices.

Supplément sur la lecture d'un bit

Il arrive régulièrement, lorsque l'on communique avec un périphérique "anormal" comme un robot ou un convoyeur, qu'on reçoive un nombre qui contient en fait plusieurs informations. Par exemple, chacun des bits du nombre peut représenter quelque chose, comme l'état de différentes parties du robot. Dans ces cas-là, le nombre reçu ne représente rien d'utile pour nous en tant que tel, mais chacun des bits nous intéresse possiblement. Il est donc utile à ce moment-là de pouvoir isoler un bit précis afin de déterminer s'il est à 1 ou à 0.

De façon similaire, plusieurs types fichiers contiennent une entête où chaque bit ou groupement de bits représente quelque chose (par exemple dans un fichier mp3).

Il existe deux façons de lire un bit donné dans un octet : par décalages successifs ou à l'aide d'un masque.

Lecture d'un bit par décalages successifs

Dans l'exemple suivant on veut obtenir la valeur du bit 5 d'un octet contenant 214 :

   !
1101 0110 (214)  
!
1011 0000 après un décalage vers la gauche de 3 positions  
        !
0000 0001 après un décalage vers la droite de 7 positions  

Après les deux décalages successifs, la valeur restante est celle du bit 5. Pour obtenir la valeur du bit n d'un octet l'algorithme est donc le suivant :

temp = octet << (8 - n);  // Comme un char a 8 bits
bit = temp >> 7;  

Lecture d'un bit à l'aide d'un masque

La deuxième façon de lire un bit utilise un masque et l'opérateur & (ET bit à bit). Appliquons cette méthode à l'exemple précédent :

     ! 
  1101 0110 (214)  
& 0001 0000 masque  (le 1 est placé à la position du bit qui nous intéresse)
  ----------
  0001 0000  
  0000 0001 après un décalage vers la droite de 4 positions

L'algorithme est le suivant :

masque = 1 << (n - 1);  // Création du masque
temp = octet & masque;  // Application du masque
bit = temp >> (n - 1);  // Décalage du bit


Laboratoire sur la manipulation de motifs binaires

Partie 1 - Opérateurs binaires

Question 0

Convertissez les nombres hexadécimaux suivant en binaire. Vérifiez vos réponses avant d'aller plus loin. Ces valeurs vous serviront pour les autres questions.

0x5A =  _ _ _ _  _ _ _ _

0x96 =  _ _ _ _  _ _ _ _

Question 1

Donnez le résultat des opérations suivantes en hexadécimal :

0x5A & 0x96 =

0x5A | 0x96 =      

Question 2
Donnez le résultat des opérations suivantes en hexadécimal :

0x5A & 0x5A =

0x5A | 0x5A =

        

Question 3
Donnez le résultat des opérations suivantes en hexadécimal :

~( 0x96 ) & 0x96 =

~( 0x96 ) | 0x96 =

        

Partie 2 (formative et autocorrigée!) - Décalages et C++

Voici un programme C++ que vous pouvez copier dans un projet sur Visual Studio. Il vous posera des questions sur les décalages. Tentez de trouver sur papier les bonnes réponses et donnez-les au programme afin de les vérifier. Notez que ces questions sont cummulatives, c'est-à-dire que les déclarations de variables et le contenu qui y est mis dans un numéro restent valides pour les numéros qui le suivent.

Vous devez donner vos réponses en hexadécimal (vous pouvez, au choix, les préfixer de 0x ou non).

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
	cout << setbase(16) << showbase;
	unsigned int xint;
	unsigned int ReponseInt;
	int Points = 0;
	
	// Question 1
	cout << "// Question 1" << endl;
	cout << "unsigned char x;" << endl << "unsigned char y = 0x57;" << endl;
	cout << "x = y << 3;" << endl << endl;
	cout << "Entrez la valeur de x en hexadecimal: ";
	cin >> hex >> ReponseInt;
	
	unsigned char x;
	unsigned char y = 0x57;
	x = y << 3;
	xint = x;

	if (ReponseInt == xint)
	{
		cout << "BRAVO!" << endl << endl;
		Points = Points + 1;
	}
	else
	{
		cout << "PERDU!  La bonne reponse est: " << xint << endl << endl;
	}
	
	// Question 2
	cout << "// Suite du programme" << endl;
	cout <<"// Question 2" << endl;
	cout << "x = y >> 2;" << endl << endl;
	cout << "Entrez la valeur de x en hexadecimal: ";
	cin >> hex >> ReponseInt;

	x = y >> 2;
	xint = x;

	if (ReponseInt == xint)
	{
		cout << "BRAVO!" << endl << endl;
		Points = Points + 1;
	}
	else
	{
		cout << "PERDU!  La bonne reponse est: " << xint << endl << endl;
	}

	// Question 3
	cout << "// Suite du programme" << endl;
	cout << "// Question 3" << endl;
	cout << "unsigned char z;" << endl << "z = y << 3;" << endl << "x = z >> 2;" << endl << endl;
	cout << "Entrez la valeur de x en hexadecimal: ";
	cin >> hex >> ReponseInt;

	unsigned char z;
	z = y << 3;
	x = z >> 2;
	xint = x;

	if (ReponseInt == xint)
	{
		cout << "BRAVO!" << endl << endl;
		Points = Points + 1;
	}
	else
	{
		cout << "PERDU!  La bonne reponse est: " << xint << endl << endl;
	}

	// Question 4
	cout << "// Suite du programme" << endl;
	cout << "// Question 4" << endl;
	cout << "y = 0xA4;" << endl << "x = y >> 2;" << endl << endl;
	cout << "Entrez la valeur de x en hexadecimal: ";
	cin >> hex >> ReponseInt;

	y = 0xA4;
	x = y >> 2;
	xint = x;

	if (ReponseInt == xint)
	{
		cout << "BRAVO!" << endl << endl;
		Points = Points + 1;
	}
	else
	{
		cout << "PERDU!  La bonne reponse est: " << xint << endl << endl;
	}

	// Question 5
	cout << "// Suite du programme" << endl;
	cout << "// Question 5" << endl;
	cout << "unsigned short w = 0x57;" << endl << "w = (w << 3) >> 2;" << endl << endl;
	cout << "Entrez la valeur de w en hexadecimal: ";
	cin >> hex >> ReponseInt;

	unsigned short w = 0x57;
	w = (w << 3) >> 2;
	xint = w;

	if (ReponseInt == xint)
	{
		cout << "BRAVO!" << endl << endl;
		Points = Points + 1;
	}
	else
	{
		cout << "PERDU!  La bonne reponse est: " << xint << endl << endl;
	}

	cout << setbase(10);
	cout << "Score final: " << Points << "/5" << endl;	
}

 

Partie 3 - Courts programmes

Question 1

Écrivez un court programme qui affiche si une valeur entrée au clavier (variable de type int) est positive ou négative. Basez-vous sur la valeur du bit de signe. Ne faites pas de if (Nombre < 0)! On veut pratiquer l'isolation d'un bit!

  • Isolez le bit de signe en utilisant un des algorithmes vus plus haut. Toutes les étapes seront-elles nécessaires dans ce cas-ci? Il est possible que non, dépendamment de la méthode choisie.
  • Vérifiez la valeur du bit de signe: si elle est 0, affichez que le nombre est positif; sinon, affichez qu'il est négatif!

Question 2

Écrivez un court programme qui affiche si une valeur entrée au clavier (variable de type int) est paire ou impaire. Basez-vous sur la valeur du premier bit. Ne faites pas de if (Nombre % 2 == 0)! On veut pratiquer l'isolation d'un bit!

  • Isolez le premier bit (celui de droite) en utilisant un des algorithmes vus plus haut. Toutes les étapes seront-elles nécessaires dans ce cas-ci? Il est possible que non, dépendamment de la méthode choisie.
  • Vérifiez la valeur du premier bit pour déterminer si le nombre est pair ou impair.

Question 3

Écrivez un court programme qui multiplie par 8 la valeur entrée au clavier (variable de type int) en utilisant le décalage.

  • Sur papier, écrivez quelques petits nombres en binaire
  • Multipliez ces nombres par 8 et écrivez les réponses en binaire
  • Identifiez le décalage qui permettrait de passer d'un nombre au nombre multiplié par 8 en analysant les bits.
  • Faites un programme qui fait faire ce même décalage au nombre entré afin de valider votre hypothèse.

Question 4

Écrivez un court programme qui divise par 4 la valeur entrée au clavier (variable de type int) en utilisant le décalage.

  • Inspirez-vous de ce que vous avez fait au numéro 3 -- cette fois-ci on divisera plutôt que de multiplier.

 

Partie 4 - Défis pour les pros (facultatifs mais donneront des points bonus!)

Question 5

Écrivez un court programme qui utilise les opérateurs sur les bits pour déterminer si la valeur entrée au clavier (variable de type int) est divisible par 16 sans reste.

  • N'utilisez pas de modulo!
  • En observant quelques nombres divisibles par 16 en binaire, tentez de trouver un pattern qui vous permettrait de les identifier.
  • Trouvez une façon de valider ce pattern dans un programme

Question 6

Simulez l'instruction de langage assembleur "RCL" (Rotate with Carry Left) sur une valeur entrée au clavier (variable de type unsigned char). L'instruction "RCL" décale tous les bits d'une position vers la gauche en réintroduisant à droite le bit perdu dans le décalage.

Par exemple RCL(10101110) = 01011101; RCL(11110000) = 11100001.

Notez que lorsque vous faites un cin pour lire quelque chose et le stocker dans un unsigned char, le programme lira uniquement le premier caractère entré au clavier et stockera son code ASCII dans la variable... Pas très utile. Pour éviter les problèmes, vous devriez faire le cin dans un unsigned int, puis transférer le contenu du unsigned int dans une variable de type unsigned char ensuite.

Vous n'avez pas besoin de valider que l'usager n'entrer que des nombres entre 0 et 255...

De la même façon si vous affichez une variable de type char à l'écran, vous verrez le caractère qui correspond au code ASCII contenu dans la variable (et non le nombre lui-même).

  • Par exemple, si la variable contient 11100001 (donc 225), la caractère au code ASCII 225 sera affiché, donc un ß apparaîtra à l'écran.

Pour voir le nombre plutôt que le caractère, vous pouvez déclarer une variable de type unsigned int, y mettre le contenu de la variable unsigned char, puis afficher la variable int. Faites ceci une fois vos opérations terminées et avant l'affichage.